package znet

import (
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
	"zinx/utils"
	zi "zinx/zInterface"
)

//连接模块
type Connection struct{
	//当前连接所属的server--以后可能会拓展为分布式的，有多个server，需要这个属性
	TCPServer zi.IServer

	//当前连接的socket TCP套接字
	Conn *net.TCPConn

	//连接的ID
	ConnID uint32

	//当前的连接状态
	isClosed bool

	//该连接处理业务方法API---即用户根据需要自定义传入
	MsgHandler zi.IMsgHandle

	// 无缓冲管道，用于读写goroutine之间通讯
	//用于reader goroutine往writer goroutine发送数据
	msgChan chan []byte

	//告知当前连接已经停止/退出的channel
	//用于reader goroutine告知writer goroutine可以退出了
	// reader goroutine读异常了，要退出时，顺便告知writer goroutine也退出
	ExitChan chan bool

	//用户自定义连接属性的集合
	property map[string]interface{}

	//保护连接属性的读写锁
	propertyLock sync.RWMutex
}

//初始化连接模块的方法
func NewConnection(server zi.IServer,conn *net.TCPConn,connID uint32,handle zi.IMsgHandle)*Connection{
	c := &Connection{
		TCPServer: server,
		Conn: conn,
		ConnID: connID,
		MsgHandler: handle,
		msgChan:make(chan []byte),
		ExitChan: make(chan bool,1), //channel通道用之前要先make一下
		property: make(map[string]interface{}),
	}
	//把新创建的连接添加到server中
	c.TCPServer.GetConnManager().Add(c)

	return c
}

// 连接读业务的方法逻辑
func(c *Connection)StartReader(){
	//先记录一下日志
	fmt.Println("Reader Goroutine is running...")
	//在结束后要把读业务的协程关闭掉
	defer func()  {
		c.Stop()
		fmt.Printf("connID = %d Reader Goroutine is exit,remote addr is %s\n",c.ConnID,c.GetRemoteAddr().String())
	}()

	//获取一个用于读取数据的dp对象
	dp := NewDataPack()
	headBuf := make([]byte,dp.GetHeadLen())
	// 用for循环，一个消息一个消息的持续的从当前连接读取数据
	for{
		//读取消息要分两次读取，第一次先读head信息，第二次根据head中的数据长度读取有效数据
		//第一次，先读取head数据
		if _,err := io.ReadFull(c.GetTCPConnection(),headBuf);err != nil{
			fmt.Printf("recv head error:%v\n",err)
			return
		}

		//把headBuf里面的数据解析出来
		msg,err := dp.UnPack(headBuf)

		//如果读取数据失败
		if err != nil{
			fmt.Printf("UnPack head error:%v\n",err)
			//这个包读取失败，下一个包继续读，所以用continue
			return
		}

		//第二次根据head中的数据长度，再次读取数据
		if msg.GetMsgLen() > 0 && msg.GetMsgLen() < utils.GlobalObject.MaxPackageSize{
			dataBuf := make([]byte,msg.GetMsgLen())
			//把有效数据读取到dataBuf中
			io.ReadFull(c.GetTCPConnection(),dataBuf)
			// 设置msg的数据
			msg.SetData(dataBuf)

			//构造一个当前连接的request对象
			req := &Request{
				conn: c,
				msg: msg,
			}

			//从路由中，找到注册绑定的conn对应的router进行调用
			//用户绑定什么业务，就用这些数据做什么事情--用户自己决定
			//将请求信息送入任务池的队列，由任务池来决定调用哪个协程进行处理
			if utils.GlobalObject.WorkerPoolSize > 0{
				c.MsgHandler.SendMsgToQueue(req)
			}else{
				panic(fmt.Sprintf("WorkerPoolSize must be > 0,but now = %d",
					utils.GlobalObject.WorkerPoolSize))
			}			
		}
	}
}

/*
	写消息的goroutine,专门发送消息给客户端的模块
 */
func(c *Connection)StartWriter(){
	//先记录一下日志
	fmt.Println("Writer Goroutine is running...")
	defer fmt.Printf("[%s] conn writer exit...\n",c.GetRemoteAddr().String())

	//不断的阻塞等待channel发送消息过来，这里可能会接到两种管道消息
	// 1.msgChan管道发送过来的数据，这些数据要经过加工再发送给客户端
	// 2.ExitChan管道发送过来的退出信息--reader那边已经退出了，writer这边也可以退出了
	for{
		select{
		//情况1，有数据要发送给客户端
		case data := <-c.msgChan:
			if _,err := c.Conn.Write(data);err != nil{
				fmt.Printf("send data to client error:%v\n",err)
				return
			}
		// 情况2，reader goroutine 已经退出，通知我们也退出
		case <-c.ExitChan:
			fmt.Println("Writer Goroutine is exit...")
			return
		}
	}
}

//启动连接--让当前连接准备开始工作
func(c *Connection)Start(){
	//先记录一下日志
	fmt.Printf("conn start,connID = %d\n",c.ConnID)
	//另起一个协程，启动当前连接读数据的业务逻辑
	go c.StartReader()

	//启动从当前连接写数据的业务逻辑
	go c.StartWriter()

	//启动连接之后使用钩子函数做点事情--这个函数由用户自己定义
	c.TCPServer.CallOnConnStart(c)
}

//停止连接--让当前连接结束工作
func(c *Connection)Stop(){
	if !c.isClosed{
		//先记录一下日志
		fmt.Printf("conn stop,connID = %d\n",c.ConnID)

		//设置连接状态
		c.isClosed = true

		//关闭连接之前，使用钩子函数做点事情--这个函数由用户自己定义
		c.TCPServer.CallOnConnStop(c)

		//关闭socket连接
		c.Conn.Close()

		//发消息通知writer goroutine 可以退出了
		c.ExitChan <- true

		// 在连接管理器中将当前连接关闭掉
		c.TCPServer.GetConnManager().Remove(c)

		//关闭资源
		close(c.ExitChan)
		close(c.msgChan)
	}
}

//获取当前连接绑定的socket conn
func(c *Connection)GetTCPConnection() *net.TCPConn{
	return c.Conn
}

//获取当前连接模块的ID
func(c *Connection)GetConnID()uint32{
	return c.ConnID
}

//获取客户端的TCP状态：IP 和 port
func(c *Connection)GetRemoteAddr()net.Addr{
	return c.Conn.RemoteAddr()
}

//发送数据--将要发送给客户端的数据，先打包，再发送
func(c *Connection)SendMsg(msgID uint32,data []byte)error{
	//如果连接已经关闭，就不能发送信息了
	if c.isClosed {
		return errors.New("connection closed when send msg")
	}

	//获取一个用于读取数据的dp对象
	dp := NewDataPack()
	//用Pack方法将msg压缩成字节流
	bytes,err := dp.Pack(NewMsgPackage(msgID,data))
	if err != nil{
		fmt.Printf("pack msg ID = %d, error:%v\n",msgID,err)
		return err
	}

	// 发送字节流给writer goroutine
	c.msgChan <- bytes
	
	return nil
}

//设置连接属性
func(c *Connection)SetProperty(key string,value interface{}){
	//操作之前先上写锁
	c.propertyLock.Lock()
	c.property[key] = value
	c.propertyLock.Unlock()
	fmt.Printf("add Property key = %s success\n",key)
}

//获取连接属性
func(c *Connection)GetProperty(key string)(interface{},error){
	//操作之前先上读锁
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()
	if p,ok := c.property[key];ok{
		return p,nil
	}
	return nil,fmt.Errorf("key = %s NOT FOUND",key)
}

//移除连接属性
func(c *Connection)RemoveProperty(key string){
	//操作之前先上写锁
	c.propertyLock.Lock()
	delete(c.property,key)
	c.propertyLock.Unlock()
	fmt.Printf("remove Property key = %s success\n",key)
}